#region Imported Namespaces
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Web;
using System.Data;
using System.Data.SqlClient;
using System.Xml;
using BO = PHSRAG.Insight21.BusinessObjects;
using DataAccess = PHSRAG.Utility.DataAccess;
using ICachePolicy = PHSRAG.Insight21.Policy.ICachePolicy;
using System.Text.RegularExpressions;
using System.Text;
#endregion

namespace PHSRAG.Insight21.FormsManagement
{
	#region Class QuestionnaireItem
	/// <summary>
	/// QuestionnaireItem abstracts the notion of a question in a Questionnaire. 
	/// 
	/// Options and Properties are contained withing QuestionnaireItem
	/// </summary>
	public class QuestionnaireItem : BO.SmartBusinessObject, ICloneable
	{
		#region Constants
		private const string QuestionnaireItemKey = "QuestionnaireItemKey_7B3248C6-9F98-47ce-9532-1E01751A95B7";

		private const string ColumnNameId = "Id";
		private const string ColumnNameVersionID = "VersionID";
		private const string ColumnNameItemName = "ItemName";
		private const string ColumnNameItemText = "ItemText";
		private const string ColumnNameItemType = "ItemType";
		private const string ColumnNameItemOrder = "ItemOrder";
		private const string ColumnNameIndent = "Indent";
		private const string ColumnNameHasSeparator = "HasSeparator";
		private const string ColumnNameRequired = "Required";
		private const string ColumnNameValidationMessage = "ValidationMessage";
		private const string ColumnNameParentName = "ParentName";
		private const string ColumnNameParentValue = "ParentValue";
		private const string ColumnNameOptionsRefDomain = "OptionsRefDomain";

		public const string LabelItemType = "Label";
		public const string TextBoxItemType = "TextBox";
		public const string CheckBoxListItemType = "CheckBoxList";
		public const string RadioButtonListItemType = "RadioButtonList";
		public const string DropDownListItemType = "DropDownList";
		public const string MultiSelectItemType = "MultiInput";
		public const string DropDownMultiSelectItemType = "DropDownMultiSelect";
		public const string CustomValueSelectorItemType = "CustomValueSelector";

		public const string ItemValidationCategory = "Form";

		#endregion

		#region Instance Variables (private)
		private int versionId;
		private string name;
		private string text;
		private string type;
		private int order;
		private bool indent;
		private bool hasSeparator = true;
		private bool required;
		private string validationMessage;
		private string parentName;
		private string parentValue;
		private string optionsRefDomain;
		private  ArrayList options;
		private Hashtable properties;
		private bool optionSourceChanged;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new QuestionnaireItem object based on the Cache Policy passed in as a parameter. 
		/// </summary>
		/// <param name="cachePolicy">The prevalent cache policy</param>
		public QuestionnaireItem(ICachePolicy cachePolicy) :  base(cachePolicy)
		{
		}

		
		/// <summary>
		/// Constructs a new QuestionnaireItem object based on the Cache Policy passed in as a parameter. 
		/// All the instance variables will be set to the corresponding values specified in the Data Row
		/// </summary>
		/// <param name="cachePolicy">The prevalent cache policy</param>
		/// <param name="dataRow">DataRow holding the values to be assigned to instance variables</param>
		public QuestionnaireItem(ICachePolicy cachePolicy, DataRow dataRow ) : base(cachePolicy)
		{
			Id	= (int)dataRow[ColumnNameId];
			versionId = (int)dataRow[ColumnNameVersionID];
			name		= (string)dataRow[ColumnNameItemName];
			
			object o;
			o = dataRow[ColumnNameItemText];
			text = (o == DBNull.Value) ? string.Empty: (string)o;
			type = (string)dataRow[ColumnNameItemType];
			order = (int)dataRow[ColumnNameItemOrder];
			
			indent = (bool)dataRow[ColumnNameIndent];

			hasSeparator	= (bool)dataRow[ColumnNameHasSeparator];

			required = (bool)dataRow[ColumnNameRequired];

			o = dataRow[ColumnNameValidationMessage];
			validationMessage = (o == DBNull.Value) ? string.Empty: (string)o;

			o = dataRow[ColumnNameParentName];
			parentName = (o == DBNull.Value) ? string.Empty : (string)o;

			o = dataRow[ColumnNameParentValue];
			parentValue = (o == DBNull.Value) ? string.Empty: (string)o;

			if (dataRow.Table.Columns.Contains(ColumnNameOptionsRefDomain))
			{
				o = dataRow[ColumnNameOptionsRefDomain];
				optionsRefDomain = (o == DBNull.Value) ? string.Empty: (string)o;
			}
		}

		#endregion

        #region ICloneable
        object ICloneable.Clone()
        {
            return Clone();
        }
        #endregion

		public QuestionnaireItem Clone()
        {
            QuestionnaireItem item = (QuestionnaireItem)MemberwiseClone( );
            item.Id = 0;
            item.Dirty = true;

            item.options = new ArrayList( );
            foreach( ItemOption option in Options )
            {
                item.options.Add( option.Clone( ) );
            }

            item.properties = new Hashtable( );
            foreach( ItemProperty property in Properties.Values )
            {
                ItemProperty cloneProperty = property.Clone( );
                item.properties.Add( cloneProperty.AttributeName, cloneProperty );
            }

            return item;
        }

		#region Properties
		/// <summary>
		/// Gets or sets the version id for the questionnaire item
		/// </summary>
		public int VersionId
		{
			get { return versionId; }
			set { SetDirty(ref versionId, value); }
		}


		/// <summary>
		/// Gets or sets the name for a QuestionnaireItem
		/// </summary>
		public string Name
		{
			get	{ return name; }
			set	{ SetDirty(ref name, value); }
		}


		/// <summary>
		/// Gets of sets the text for the questionnaire item
		/// </summary>
		public string Text
		{
			get	{ return text; }
			set	{ SetDirty(ref text, value); }
		}


		/// <summary>
		/// Gets the question text after stripping all the html tags
		/// </summary>
		public string HtmlStrippedText
		{
			get { return (text != null) ? Regex.Replace(text, "<[^>]+>", new MatchEvaluator(QuestionnaireItem.StripHtml)) : string.Empty; }
		}

		
		/// <summary>
		/// Gets or sets the type of the QuestionnaireItem
		/// </summary>
		public virtual string Type
		{
			get { return type; }
			set { SetDirty(ref type, value); }
		}


		/// <summary>
		/// Gets or sets the order of the item
		/// </summary>
		public int Order
		{
			get { return order; }
			set { SetDirty(ref order, value); }
		}


		/// <summary>
		/// Gets/sets whether or not the question should be indented
		/// </summary>
		public bool Indent
		{
			get { return indent; }
			set { SetDirty(ref indent, value); }
		}
		

		/// <summary>
		/// Gets or sets whether or not a seperator (line) is displayed after the item
		/// </summary>
		public bool HasSeparator
		{
			get	{ return hasSeparator; }
			set	{ SetDirty(ref hasSeparator, value); }
		}
	

		/// <summary>
		/// Gets or sets the validation message to be dispalyed when the item is not answered
		/// </summary>
		public string ValidationMessage
		{
			get	{ return validationMessage;	}
			set	{ SetDirty(ref validationMessage, value); }
		}
		

		/// <summary>
		/// Gets or sets whether or not the item is required
		/// </summary>
		public bool Required
		{
			get { return required; }
			set { SetDirty(ref required, value); }
		}


		/// <summary>
		/// Gets or sets the name of the parent item
		/// </summary>
		public string ParentName
		{
			get { return parentName; }
			set { SetDirty(ref parentName, value); }
		}


		/// <summary>
		/// Gets or sets the value of the parent item which would cause this item to be displayed
		/// </summary>
		public string ParentValue
		{
			get { return parentValue; }
			set { SetDirty(ref parentValue, value); }
		}


		/// <summary>
		/// Gets or sets the value of the parent item which would cause this item to be displayed
		/// </summary>
		public string OptionsRefDomain
		{
			get { return optionsRefDomain; }
			set { SetDirty(ref optionsRefDomain, value); }
		}


		/// <summary>
		/// Gets or sets the collection of options for the questionnaire item
		/// </summary>
		public virtual ArrayList Options
		{
			get
			{
				if (options == null)
				{
					if (optionsRefDomain == null || optionsRefDomain == string.Empty)
						options = ItemOption.GetQuestionnaireItemOptions(CachePolicy, Id);
					else
					{
						options = ItemOption.GetQuestionnaireItemOptions(CachePolicy, optionsRefDomain);
					}
				}
				return options;
			}
			set	{ options = value; }
		}
		

		/// <summary>
		/// Gets or sets the property collection for the questionnaire item
		/// </summary>
		public virtual Hashtable Properties
		{
			get
			{
				if (properties == null)
				{
					properties = ItemProperty.GetQuestionnaireItemProperties(CachePolicy, Id);
				}
				return properties;
			}
			set { properties = value; }
		}

		public bool OptionSourceChanged
		{
			get { return optionSourceChanged; }
			set { optionSourceChanged = value; }
		}
		#endregion

		#region SmartBusinessObjects
		/// <summary>
		/// Cache Key uniquely identifies this object within the cache.
		/// </summary>
		/// <returns>unique string for this object.</returns>
		public override string CacheKey()
		{
			return QuestionnaireItemKey;
		}
		
		
		/// <summary>
		/// Cache this instance using the prevalent cache policy.
		/// </summary>
		public override void Cache()
		{
			CachePolicy.Cache(QuestionnaireItemKey, this);
		}

		
		/// <summary>
		/// Remove this instance from the cache using the prevalent cache policy.
		/// </summary>
		public override void UnCache()
		{
			CachePolicy.UnCache(QuestionnaireItemKey);
		}
		
		
		/// <summary>
		/// Determines if the object is dirty based on whether or not atleast one of its properties have changed or any of its contained objects have changed
		/// </summary>
		/// <returns>true - if the object is dirty or any of its contained objects are dirty, false - if not.</returns>
		public override bool IsDirty()
		{
			if (Dirty)
				return true;

			if (options != null)
			{
				foreach(ItemOption o in options)
				{
					if (o.IsDirty())
						return true;
				}
			}
			if (properties != null)
			{
				foreach(ItemProperty o in properties.Values)
				{
					if (o.IsDirty())
						return true;
				}
			}
			return false;
		}
		
		
		/// <summary>
		/// Loads the QuestionnaireItem object based on the id passed in as parameter
		/// <para>This method is not yet implemented</para> 
		/// </summary>
		/// <param name="connectionString">Information to connect to the database</param>
		/// <param name="args">QuestionnaireItem Id of the object to be loaded</param>
		public override void Load(string connectionString, params object[] args)
		{
			throw new Exception("Not Implemented");
		}
		 
		
		/// <summary>
		/// Saves the state of the QuestionnaireItem object to the database. 
		/// The stored procedure inserts a new record if the id of the object is 0, 
		/// otherwise the procedure updates the record with the matching questionnaireitem id
		/// </summary>
		/// <param name="sqlTransaction">The database context in which the underlying sql statement needs to be executed</param>
		protected override void SaveObject(SqlTransaction sqlTransaction)
		{
			try
			{
				Id = Convert.ToInt32(Utility.DataAccess.ExecuteScalar(sqlTransaction.Connection,sqlTransaction, "SaveQuestionnaireItem",
					Utility.DataAccess.BuildSqlParameterArray(
					"@id", SqlDbType.Int, Id
					,"@versionID", SqlDbType.Int, versionId
					,"@itemName", SqlDbType.VarChar, name
					,"@itemText", SqlDbType.VarChar, text
					,"@itemType", SqlDbType.VarChar, Type
					,"@itemOrder", SqlDbType.Int, order
					,"@hasSeparator", SqlDbType.Bit, hasSeparator
					,"@indent", SqlDbType.Int, indent
					,"@parentName", SqlDbType.VarChar, (parentName == null) ? DBNull.Value : (object)parentName
					,"@parentValue", SqlDbType.VarChar, (parentValue == null) ? DBNull.Value : (object)parentValue
					,"@required", SqlDbType.Bit, required
					,"@validationMessage", SqlDbType.VarChar, (validationMessage == null) ? DBNull.Value : (object)validationMessage
					,"@optionsRefDomain", SqlDbType.VarChar, (optionsRefDomain == null || optionsRefDomain == string.Empty) ? DBNull.Value : (object)optionsRefDomain
					)));

				if (optionSourceChanged)
				{
					Utility.DataAccess.ExecuteNonQuery(sqlTransaction.Connection, sqlTransaction, "DeleteQuestionnaireItemDependencies",
						Utility.DataAccess.BuildSqlParameterArray(
						"@itemId", SqlDbType.Int, Id));
				}
				if (optionsRefDomain != null && OptionsRefDomain != string.Empty)
				{
					Utility.DataAccess.ExecuteNonQuery(sqlTransaction.Connection, sqlTransaction, "DeleteQuestionnaireItemOptions",
						Utility.DataAccess.BuildSqlParameterArray(
						"@itemId", SqlDbType.Int, Id));

					options = null;
				}
				else if (options != null)
				{
					//Saving ItemOption objects
					//Options could be marked as deleted. The following logic will call Delete on objects marked for deletion
					//and remove them from the collection and as such a foreach loop is not used. The variable 'index' is a pointer
					//to the object in the collection that is being processed. The 'count' keeps track of the number of objects in 
					//the collection. The count will change whenever there is an object that is marked for deletion.
					int index = 0;
					int count = options.Count;
					while ( index < count)
					{
						ItemOption option = (ItemOption)options[index];
						option.QuestionnaireItemId = Id;
						option.Save(sqlTransaction);
						if (!option.MarkForDelete)
						{
							++index;
						}
						else
						{
							options.RemoveAt(index);
							--count;
						}
					}
				}

				if (properties != null)
				{
					StringBuilder deletedPropertiesList = new StringBuilder();
					foreach(ItemProperty property in properties.Values)
					{
						property.QuestionnaireItemId = Id;
						property.Save(sqlTransaction);
						//store the names of the properties that are deleted 
						if (property.MarkForDelete)
						{
							deletedPropertiesList.Append(string.Format("{0};",property.AttributeName));
						}
					}
					string[] deletedProperties = deletedPropertiesList.ToString().Split(';');
					for(int i=0, count = deletedProperties.Length -1; i<count; ++i)
						properties.Remove(deletedProperties[i]);
				}
			}
			catch(Exception ex)
			{
				Utility.ExceptionReport.WriteToEventLog("QuestionnaireItem.SaveObject: Failed to save the QuestionnaireItem object to the database", 
					ex, true, Utility.ExceptionReport.ReportType.Error);
				throw new Exception("QuestionnaireItem.SaveObject: Failed to save the QuestionnaireItem object to the database", ex);
			}
		}
		
		
		/// <summary>
		/// Deletes the record represented by the object from the database
		/// </summary>
		/// <param name="sqlTransaction">The database context in which the underlying sql statement needs to be executed</param>
		protected override void DeleteObject(SqlTransaction sqlTransaction)
		{
			try
			{
				Utility.DataAccess.ExecuteNonQuery(sqlTransaction.Connection, sqlTransaction, "DeleteQuestionnaireItem",
					Utility.DataAccess.BuildSqlParameterArray(
					"@id", SqlDbType.Int, Id
					));
			}
			catch(Exception ex)
			{
				Utility.ExceptionReport.WriteToEventLog("QuestionnaireItem.DeleteObject: Failed to delete the QuestionnaireItem object from the database", 
					ex, true, Utility.ExceptionReport.ReportType.Error);
				throw new Exception("QuestionnaireItem.DeleteObject: Failed to delete the QuestionnaireItem object from the database", ex);
			}
		}

		#endregion

		#region Static methods
		/// <summary>
		/// Gets all the Questionnaire Item's that belong to the Questionnaire version with the specified id
		/// </summary>
		/// <param name="cachePolicy">The prevalent cache policy</param>
		/// <param name="questionnaireVersionID">The id the Questionnaire Version of which the Questionnaire Items have to be fetched</param>
		/// <returns>A hashtable of questionnaire items with id as the key and the objec as the value</returns>
		public  static ArrayList GetQuestionnaireVersionItems(ICachePolicy cachePolicy, int questionnaireVersionID)
		{
			ArrayList itemList = new ArrayList();
			try
			{
				DataSet ds = Utility.DataAccess.GetDataSet(Utility.DataAccess.GetConnectionString(), "GetQuestionnaireItemsForVersion", 
					Utility.DataAccess.BuildSqlParameterArray
					("@questionnaireVersionID", SqlDbType.Int, questionnaireVersionID));
				if (ds.Tables.Count > 0)
				{
					foreach( DataRow row in ds.Tables[0].Rows )
					{
						QuestionnaireItem item = new QuestionnaireItem(cachePolicy, row);
						itemList.Add(item);
					}
				}
				else
				{
					throw new Exception("GetQuestionnaireItemsForVersion stored procedure returned incorrect results");
				}
			}
			catch(Exception ex)
			{
				Utility.ExceptionReport.WriteToEventLog ("Failed to Load Questionnaire Version Items", ex, true, Utility.ExceptionReport.ReportType.Error);
				throw new Exception("Failed to Load Questionnaire Version Items", ex);
			}

			return itemList;
		}

		public static string StripHtml(Match m)
		{
			string x = m.ToString(); //matched string
			if (x.ToLower().IndexOf("<br ") == 0 || x.ToLower().IndexOf("<p ") == 0)
			{
				return "\r\n";
			}
			return string.Empty;
		}
		#endregion

        /// <summary>
        /// return int value of Log Item handler page
        /// </summary>
        public int GetLogItemHandler(string logMsg)
        {
            foreach (string msg in StaffValidationMsg)
            {
                if (msg.Contains(logMsg))
                {
                    return (int)FormsManagement.ValidationFormType.StaffAndAccess;
                
                }
            }

            return (int) FormsManagement.ValidationFormType.AnswerEditor;
        }

        /// <summary>
        /// Gets or sets the staffValidationMsg to handle the page to user in case validation error occured
        /// </summary>
        /// <remarks>to do: implement a db load method or add to existing load method to populate the staffValidationMsg arraylist with data from db</remarks>
        public ArrayList StaffValidationMsg
        {
            get
            {
                if (staffValidationMsg == null)
                {
                    staffValidationMsg = new ArrayList();
                    staffValidationMsg.Add("You must add or remove the study personnel from the Staff & Access page.");
                }

                return staffValidationMsg;
            }
            set
            {
                staffValidationMsg = value;
            }
        }

        private ArrayList staffValidationMsg;
		
	}
	#endregion
}

